package com.partner.partnerback.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.partner.partnerback.common.Constants;
import com.partner.partnerback.common.enums.EmailCodeEnum;
import com.partner.partnerback.controller.domain.LoginDTO;
import com.partner.partnerback.controller.domain.UserRequest;
import com.partner.partnerback.entity.User;
import com.partner.partnerback.exception.ServiceException;
import com.partner.partnerback.mapper.UserMapper;
import com.partner.partnerback.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.partner.partnerback.utils.EmailUtils;
import com.partner.partnerback.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Hillky
 * @since 2022-12-26
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private static final Map<String, Long> CODE_MAP = new ConcurrentHashMap<>();

    private static final Integer FIVE_MINUTES = 5 * 60 * 1000;

    @Autowired
    private EmailUtils emailUtils;

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public LoginDTO login(UserRequest user) {
        String username = user.getUsername();
        User dbUser = this.getOne(new QueryWrapper<User>().eq("username", username).or().eq("email", user.getEmail()));
        if (dbUser == null) {
            throw new ServiceException("未找到用户");
        }

//        if (!BCrypt.checkpw(user.getPassword(), dbUser.getPassword())) {
//            throw new ServiceException("用户名或密码错误");
//        }


        String ciphertext = SaSecureUtil.aesEncrypt(Constants.PASSWORD_KEY,   user.getPassword());
        if(!ciphertext.equals(dbUser.getPassword())){
            throw new ServiceException("用户名或密码错误");
        }
        // 第1步，先登录上
        StpUtil.login(dbUser.getUid());
        // 设置用户对象到缓存
        StpUtil.getSession().set(Constants.LOGIN_USER_KEY, dbUser);
        // 第2步，获取 Token  相关参数
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        log.info("tokenInfo: {}", tokenInfo);
        // 获取 token
        String tokenValue = tokenInfo.getTokenValue();
        return new LoginDTO(dbUser, tokenValue);
    }

    @Override
    public User register(UserRequest userRequest) {
        // 校验邮箱
        String key = Constants.EMAIL_CODE + EmailCodeEnum.REGISTER.getValue() + userRequest.getEmail();
        validateEmail(key, userRequest.getEmailCode());

        //1.参数校验且设默认值
        if (StrUtil.isBlank(userRequest.getName())) {
            userRequest.setName("李白");
        }
        //2.重复性判断
        User dbUser = this.getOne(new QueryWrapper<User>().eq("username", userRequest.getUsername()).or().eq("email", userRequest.getEmail()));
        if (ObjectUtil.isNotNull(dbUser)) {
            throw new ServiceException("用户名已注册");
        }
        //3.添加到数据库
        User user = new User();
        BeanUtils.copyProperties(userRequest, user);
        String ciphertext = SaSecureUtil.aesEncrypt(Constants.PASSWORD_KEY,   user.getPassword());
        user.setPassword(ciphertext);
        user.setUid(IdUtil.fastSimpleUUID());
        boolean success = this.save(user);
        if (!success) {
            throw new ServiceException("注册失败");
        }
        return user;

    }

    @Override
    public void sendEmail(String email, String type) {
        String emailPrefix = EmailCodeEnum.getValue(type);
        if (StrUtil.isBlank(emailPrefix)) {
            throw new ServiceException("不支持的邮箱验证类型");
        }
        //先获取redis中的已发送的code
        String key = Constants.EMAIL_CODE + emailPrefix + email;
        Long expireTime = RedisUtils.getExpireTime(key);
        // 限制超过1分钟才可以继续发送邮件，判断过期时间是否大于4分钟
        if (expireTime != null && expireTime > 4 * 60) {
            throw new ServiceException("发送邮箱验证过于频繁");
        }


        Integer code = Integer.valueOf(RandomUtil.randomNumbers(6));
        log.info("本次的验证码是：{}", code);
        String context = "<b>尊敬的用户：</b><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;您好，" +
                "Partner交友网提醒您本次的验证码是：<b>{}</b>，" +
                "有效期5分钟。<br><br><br><b>Partner交友网</b>";
        String html = StrUtil.format(context, code);
        // 校验邮箱是否已注册
        User user = getOne(new QueryWrapper<User>().eq("email", email));

        if (EmailCodeEnum.REGISTER.equals(EmailCodeEnum.getEnum(type))) {  // 无需权限验证即可发送邮箱验证码
            if (user != null) {
                throw new ServiceException("邮箱已注册");
            }
        } else if (EmailCodeEnum.RESET_PASSWORD.equals(EmailCodeEnum.getEnum(type))) {
            if (user == null) {
                throw new ServiceException("未找到用户");
            }
        }

        ThreadUtil.execAsync(() -> {
            emailUtils.sendHtml("【Partner交友网】验证提醒", html, email);
        });

        RedisUtils.setCacheObject(key, code, FIVE_MINUTES, TimeUnit.MILLISECONDS);
    }

    @Override
    public String passwordReset(UserRequest userRequest) {
        String email = userRequest.getEmail();

        // 校验邮箱验证码
        String key = Constants.EMAIL_CODE + EmailCodeEnum.RESET_PASSWORD.getValue() + email;
        validateEmail(key, userRequest.getEmailCode());

        //找用户
        User dbUser = getOne(new UpdateWrapper<User>().eq("email", email));
        if (dbUser == null) {
            throw new ServiceException("未找到用户");
        }
        String newPass = "123";
        String ciphertext = SaSecureUtil.aesEncrypt(Constants.PASSWORD_KEY, newPass);
        dbUser.setPassword(ciphertext);
        try {
            updateById(dbUser);   // 设置到数据库
        } catch (Exception e) {
            throw new RuntimeException("重置密码失败", e);
        }
        return newPass;

    }

    private void validateEmail(String emailKey, String emailCode) {
        // 校验邮箱
        Integer code = RedisUtils.getCacheObject(emailKey);
        if (code == null) {
            throw new ServiceException("验证码失效");
        }
        if (!emailCode.equals(code.toString())) {
            throw new ServiceException("验证码错误");
        }
        RedisUtils.deleteObject(emailKey);  // 清除缓存
    }
}
